package cn.gs.global.server.timer;

// @author gs
import cn.gs.exchanger.ExchangerClient;
import cn.gs.exchanger.ExchangerException;
import cn.gs.exchanger.java.JavaExchangerClient;
import cn.gs.global.common.timer.ITimerControl;
import cn.gs.global.common.timer.ITimerListener;
import cn.gs.global.common.timer.TimerClientTool;
import cn.gs.reflect.PropertyTool;
import cn.gs.util.monitor.ITimerMonitorTask;
import cn.gs.util.monitor.TimerMonitor;
import cn.gs.util.set.Pair;
import java.io.IOException;
import java.lang.reflect.UndeclaredThrowableException;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;

public class TimerServer implements ITimerControl {

    public static void main(String[] args) throws InterruptedException, UnknownHostException, IOException {
        TimerMonitor tm = new TimerMonitor();
        //tm.start();
        JavaExchangerClient client = new JavaExchangerClient("127.0.0.1", ExchangerClient.PORT);
        TimerServer sv = new TimerServer(client, tm);
        sv.registerService();
        sv.resume();
        tm.join();
    }
    volatile boolean paused = false;
    TimerMonitor monitor;
    JavaExchangerClient client;
    Map<String, ITimerListener> listeners = new HashMap<>();

    public TimerServer(JavaExchangerClient client, TimerMonitor monitor) {
        this.client = client;
        this.monitor = monitor;
    }

    public void findClient() {
        try {
            int num, retry = 5;
            do {
                List<Integer> numList = client.serviceProviderCount("gs.timer.find");
                num = numList.get(0);
                for (int i = 0; i < num; ++i) {
                    Pair<List<String>, ExchangerException> t = ((ExchangerClient) client).ask(TimerClientTool.FIND_COMMAND);
                    if (t != null && t.getFirst() != null && t.getFirst().size() > 0) {
                        String name = t.getFirst().get(0);
                        if (!listeners.containsKey(name)) {
                            ITimerListener q = client.bind(ITimerListener.class, TimerClientTool.TIMER_INSANCE, name);
                            listeners.put(name, q);
                            Logger.getLogger(TimerServer.class.getName()).log(Level.INFO, "Find_Client:{0}", name);
                        }
                    }
                }
            } while (num != listeners.size() && --retry > 0);
        } catch (ExchangerException ex) {
            Logger.getLogger(TimerServer.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void registerService() {
        try {
            client.register(this, ITimerControl.class);
        } catch (ExchangerException ex) {
            Logger.getLogger(TimerServer.class.getName()).log(Level.SEVERE, null, ex);
        }
        findClient();
        monitor.add(new Hearteat(), CTimerSetting.interval, 1);
    }

    @Override
    public void pause() {
        paused = true;
    }

    @Override
    public void resume() {
        paused = false;
    }

    @Override
    public void setDate(int date) throws ExchangerException {
        CTimerSetting.date = date;
        CTimerSetting.checkedDay = date;
        CTimerSetting.checkedMonth = date / 30;
        CTimerSetting.checkedYear = date / 30 / 12;
    }

    private enum EType {

        YEAR, MONTH, DAY, HEARTBEAT
    }

    void newCall(int num, EType type) {
        Iterator<Entry<String, ITimerListener>> itr = listeners.entrySet().iterator();
        while (itr.hasNext()) {
            Entry<String, ITimerListener> i = itr.next();
            try {
                switch (type) {
                    case YEAR:
                        i.getValue().onNewYear(num);
                        break;
                    case MONTH:
                        i.getValue().onNewMonth(num);
                        break;
                    case DAY:
                        i.getValue().onNewDay(num);
                        break;
                    case HEARTBEAT:
                        i.getValue().onHeartbeat(num);
                        break;
                }
            } catch (Throwable ex) {
                Logger.getLogger(TimerServer.class.getName()).log(Level.WARNING, type.name() + "_Call_Error", ex);
                while (ex != null && ex instanceof UndeclaredThrowableException) {
                    ex = ex.getCause();
                }
                if (ex instanceof ExchangerException) {
                    if (((ExchangerException) ex).getType().equals(ExchangerException.Type.SERVER_NOT_EXIST)) {
                        itr.remove();
                    }
                }
            }
        }
        PropertyTool.saveProperties();
    }

    class Hearteat implements ITimerMonitorTask {

        int timesADay = 0;

        @Override
        public void start() {
        }

        @Override
        public boolean retry() {
            if (paused) {
                return false;
            }
            ++timesADay;
            if (timesADay > CTimerSetting.timesOfADay) {
                findClient();
                //New Day
                ++CTimerSetting.date;
                timesADay = 1;
                PropertyTool.saveProperties();
            }
            int month = CTimerSetting.date / 30;
            int year = month / 12;
            if (CTimerSetting.checkedYear < year) {
                ++CTimerSetting.checkedYear;
                Logger.getLogger(TimerServer.class.getName()).log(Level.INFO, "New_Year:{0}", CTimerSetting.checkedYear);
                newCall(CTimerSetting.checkedYear, EType.YEAR);
                PropertyTool.saveProperties();
            } else if (CTimerSetting.checkedMonth < month) {
                ++CTimerSetting.checkedMonth;
                Logger.getLogger(TimerServer.class.getName()).log(Level.INFO, "New_Month:{0}", CTimerSetting.checkedMonth);
                newCall(CTimerSetting.checkedMonth, EType.MONTH);
                PropertyTool.saveProperties();
            } else if (CTimerSetting.checkedDay < CTimerSetting.date) {
                ++CTimerSetting.checkedDay;
                Logger.getLogger(TimerServer.class.getName()).log(Level.INFO, "New_Day:{0}", CTimerSetting.checkedDay);
                newCall(CTimerSetting.checkedDay, EType.DAY);
                PropertyTool.saveProperties();
            } else {
                Logger.getLogger(TimerServer.class.getName()).log(Level.INFO, "Heart_Beat_:{0}", timesADay);
                newCall(CTimerSetting.checkedDay, EType.HEARTBEAT);
            }
            monitor.add(this, CTimerSetting.interval, 1);
            return false;
        }

        @Override
        public void fail() {
        }
    }
}
